using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;

namespace Atomic.Libraries.Compression
{
	/// <summary>
	/// An data storage class where the content is only compressed upon serialization. After the compression, the uncompressed
	/// representation of the data is erased, and each subsequent read is performed by uncompression.
	/// </summary>
	[Serializable]
	public class CompressedData : IBinaryData
	{
		private byte[] dataCompressed;
		[NonSerialized] private byte[] data;

		public CompressedData(byte[] data)
		{
			if (data == null)
			{
				throw new ArgumentNullException();
			}

			this.data = (byte[])data.Clone();
		}

		private CompressedData(byte[] data, byte[] dataCompressed)
		{
			this.data = data;
			this.dataCompressed = dataCompressed;
		}

		public static CompressedData Read(FileInfo file)
		{
			// Avoid the copying in the public constructor.
			return new CompressedData(File.ReadAllBytes(file.FullName), null);
		}

		public static CompressedData ReadCompressed(FileInfo file)
		{
			if (!file.FullName.EndsWith(".bz2"))
			{
				throw new ArgumentException("The file must be a bzip2 compressed file with extension .bz2.");
			}

			// Avoid the copying in the public constructor.
			return new CompressedData(null, File.ReadAllBytes(file.FullName));
		}

		/// <summary>
		/// Forces compression of the representation of the data. This is automatically performed upon serialization.
		/// </summary>
		public void Compress()
		{
			if (dataCompressed != null)
			{
				// Already compressed.
				return;
			}

			// Use maximum compression level for long-term storage.
			dataCompressed = BZip2.Compress(data, 9);

			// From this point on, the uncompressed version isn't stored anymore.
			data = null;
		}

		public void Decompress()
		{
			if (data != null)
			{
				// Already decompressed.
				return;
			}

			data = BZip2.Decompress(dataCompressed);
			dataCompressed = null;
		}

		public byte[] GetData()
		{
			if (data != null)
			{
				// An uncompressed version is stored. Just return this.
				return (byte[])data.Clone();
			}

			return BZip2.Decompress(dataCompressed);
		}

		[OnSerializing]
		private void OnSerializing(StreamingContext context)
		{
			// Store the compressed representation of the data just before serializing.
			Compress();
		}
	}
}
